/*
 * Service.java
 *
 * Created on April 22, 2007, 1:00 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.agent.services;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.data.Parameter;
import org.restlet.util.Series;

/**
 *
 * @author alex
 */
public class Service
{
   
   List<URL> jars;
   URI interfaceURI;
   URI location;
   String path;
   Name name;
   String className;
   String origClassName;
   String origPath;
   ClassLoader classLoader;
   Class implClass;
   Map<String,String> parameters;
   ServiceMetadata metadata;
   
   /** Creates a new instance of Service */
   public Service(Name name,String path,URI location)
   {
      this(null,name,path,location,null);
   }
   public Service(Name name,String path,String className)
   {
      this(null,name,path,null,className);
   }
   public Service(Name name,String path,URI location,String className)
   {
      this(null,name,path,location,className);
   }
   public Service(URI interfaceURI,Name name,String path,URI location)
   {
      this(interfaceURI,name,path,location,null);
   }
   public Service(URI interfaceURI,Name name,String path,String className)
   {
      this(interfaceURI,name,path,null,className);
   }
   public Service(URI interfaceURI,Name name,String path,URI location,String className)
   {
      this.interfaceURI = interfaceURI;
      this.name = name;
      this.path = path;
      this.origPath = path;
      this.location = location;
      this.className = className;
      this.origClassName = className;
      this.parameters = null;
      this.jars = null;
      this.classLoader = Service.class.getClassLoader();
      this.implClass = null;
      this.metadata = null;
   }
   
   public ServiceMetadata getMetadata() {
      return metadata;
   }
   
   public void save(URI baseURI,ItemConstructor constructor,ItemDestination dest)
      throws XMLException
   {
      Element serviceE = constructor.createElement(ServiceRegistry.SERVICE);
      if (origPath!=null) {
         serviceE.setAttributeValue("path",origPath);
      }
      URI u = name.getNamespaceName();
      String prefix = "";
      if (!Name.NO_NAMESPACE.equals(u)) {
         serviceE.addNamespaceBinding("n", u);
         prefix = "n:";
      }
      serviceE.setAttributeValue("name",prefix+name.getLocalName());
      serviceE.setAttributeValue("interface",interfaceURI.toString());
      if (origClassName!=null) {
         serviceE.setAttributeValue("class",origClassName);
      }
      if (location!=null) {
         serviceE.setAttributeValue("href",baseURI.relativize(location).toString());
      }
      dest.send(serviceE);
      for (String name : parameters.keySet()) {
         Element paramE = constructor.createElement(ServiceRegistry.PARAMETER);
         paramE.setAttributeValue("name",name);
         paramE.setAttributeValue("value",parameters.get(name));
         dest.send(paramE);
         dest.send(constructor.createElementEnd(ServiceRegistry.PARAMETER));
      }
      if (this.jars!=null) {
         for (URL url : jars) {
            try {
               Element libE = constructor.createElement(ServiceRegistry.LIBRARY);
               libE.setAttributeValue("href",baseURI.relativize(url.toURI()).toString());
               dest.send(libE);
               dest.send(constructor.createElementEnd(ServiceRegistry.LIBRARY));
            } catch (URISyntaxException ex) {
               throw new XMLException("Cannot convert URL to URI",ex);
            }
         }
      }
      dest.send(constructor.createElementEnd(ServiceRegistry.SERVICE));
   }
   
   public Map<String,String> getParameters() 
   {
      if (parameters==null) {
         parameters = new TreeMap<String,String>();
      }
      return parameters;
   }
   
   public void setParameters(Map<String,String> parameters)
   {
      this.parameters = parameters;
   }
   
   public void setLibraries(List<URL> jars) {
      this.jars = jars;
   }

   public URI getInterface()
   {
      return interfaceURI;
   }

   public URI getLocation()
   {
      return location;
   }

   public String getPath()
   {
      return path;
   }

   public Name getName()
   {
      return name;
   }
   
   public String getClassName()
   {
      return className;
   }
   
   public Application newApplication(Context context)
      throws NoSuchMethodException,InstantiationException,IllegalAccessException,InvocationTargetException
   {
      if (parameters!=null) {
         Series<Parameter> series = context.getParameters();
         for (String name : parameters.keySet()) {
            series.add(name,parameters.get(name));
         }
      }
      Constructor mk = implClass.getConstructor(Context.class);
      return (Application)mk.newInstance(context);
   }

   public static ServiceMetadata getServiceMetadata(Class serviceClass) {
      Annotation[] classAnnotations = serviceClass.getAnnotations();

      for(Annotation annotation : classAnnotations) {
         if (annotation instanceof ServiceMetadata) {
            return (ServiceMetadata)annotation;
         }
      }
      return null;
   }
   
   public void init() 
      throws IOException,XMLException,ClassNotFoundException
   {
      if (location==null && className==null) {
         throw new IllegalStateException("Both the location and class are null.");
      } else if (location!=null) {
         URL [] urls = new URL[jars==null ? 1 : (jars.size()+1)];
         urls[0] = location.toURL();
         if (jars!=null) {
            for (int i=0; i<jars.size(); i++) {
               urls[i+1] = jars.get(i);
            }
         }
         classLoader = new URLClassLoader(urls,this.getClass().getClassLoader());
         if (className==null) {
            URL appDescriptorLoc = classLoader.getResource("application.xml");
            if (appDescriptorLoc==null) {
               throw new IOException("Cannot file application.xml in class path.");
            }
            try {
               DocumentLoader loader = new SAXDocumentLoader();
               Document appDesc = loader.load(appDescriptorLoc.toURI());
               className = appDesc.getDocumentElement().getAttributeValue("class");
               if (className==null) {
                  throw new IOException("Cannot find class name in application.xml");
               }
               if (path==null) {
                  path = appDesc.getDocumentElement().getAttributeValue("path");
               }
            } catch (URISyntaxException ex) {
               throw new IOException(ex.getMessage());
            }
         }
      }
      if (path==null) {
         path = "";
      }
      implClass = classLoader.loadClass(className);
      metadata = getServiceMetadata(implClass);
      
      if (interfaceURI==null && metadata!=null) {
         if (metadata.id()!=null) {
            try {
               interfaceURI = new URI(metadata.id());
            } catch (URISyntaxException ex) {
               throw new IOException("Cannot create interface URI from: "+metadata.id());
            }
         }
      }
      
      //System.out.println("metadata="+metadata);
      //System.out.println("interfaceURI="+interfaceURI);
      if (interfaceURI==null) {
         throw new IOException("The interface is missing.");
      }
   }
   
}
